/*
 * Copyright (C) 2009 M. Hautle.
 * 
 * This file is part of TrackedBean.
 * 
 * TrackedBean is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General
 * Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 * 
 * TrackedBean is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with TrackedBean. If not, see
 * <http://www.gnu.org/licenses/>.
 */
package bmodeling;

import java.net.*;
import java.util.*;

/**
 * Node representing a package and holding the jars containing this package.
 * 
 * @author M. Hautle
 * @since 14.09.08
 */
public final class Node {
	/** The child nodes of this node */
	private final Map<String, Node> childs = new HashMap<String, Node>();

	/** All jars which may contain a ressource of this node or his sub nodes */
	private final ArrayList<Jar> jars = new ArrayList<Jar>(1);

	/**
	 * Default constructor
	 */
	public Node() {
	}

	/**
	 * Default constructor
	 * 
	 * @param jar The jar which contains the ressoruces of this node
	 */
	private Node(final Jar jar) {
		jars.add(jar);
	}

	/**
	 * Adds the given node under the given name
	 * 
	 * @param name The node name
	 * @param n The node
	 * @return The added node
	 */
	private Node addNode(final String name, final Node n) {
		childs.put(name, n);
		return n;
	}

	/**
	 * Adds the given jar to this node if it isn't allready contained in it.
	 * 
	 * @param jar The jar to add
	 */
	public void addJar(final Jar jar) {
		if (!jars.contains(jar))
			jars.add(jar);
	}

	/**
	 * Return a list of all ressources with the given name
	 * 
	 * @param name A file name (with / as package seperator)
	 * @return A list of all ressources with the given name
	 * @throws MalformedURLException
	 */
	public ArrayList<URL> findResources(String name) throws MalformedURLException {
		final ArrayList<URL> res = new ArrayList<URL>();
		// search all jars for the requested ressource name
		for (Jar f : jars)
			if (f.jar.getJarEntry(name) != null)
				res.add(f.getEntryUrl(name));
		return res;
	}

	/**
	 * Looks up the file with the given name<br>
	 * This methode is just for the root node!
	 * 
	 * @param name A file name (with / as package seperator)
	 * @param entry An {@link Entry} object where to put the result
	 * @return The passed entry object containing the resolved entry or null if nothing was found
	 */
	public Entry lookup(final String name, final Entry entry) {
		final String[] nameParts = name.split("/");
		// try first to look up the entry
		Entry res = lookup(name, nameParts, 0, entry);
		if (res != null)
			return res;
		// the lookup failed, so we will search the entry
		res = searchEntry(name, entry);
		// if the entry was found by the search, then we add it into the lookup tree
		if (res != null)
			mergeNodePath(nameParts, 0, res.parent);
		return res;
	}

	/**
	 * Looks up the file with the given name
	 * 
	 * @param name A file name (with / as package seperator)
	 * @param nameParts The file name splitted up into directories
	 * @param off The index of the directory in <code>name</code> to search now
	 * @param entry An {@link Entry} object where to put the result
	 * @return The passed entry object containing the resolved entry or null if nothing was found
	 */
	private Entry lookup(final String name, final String[] nameParts, final int off, final Entry entry) {
		final int jarCnt = jars.size();
		// if theres only one jar, then do the lookup directly
		if (jarCnt == 1)
			return jars.get(0).getEntry(name, entry);
		// otherwise try to walk down the path (if there is a corresponding node)
		final Node child = off + 1 < nameParts.length ? childs.get(nameParts[off]) : null;
		if (child != null)
			return child.lookup(name, nameParts, off + 1, entry);
		// travell throug all jars in this node
		for (Jar j : jars) {
			final Entry e = j.getEntry(name, entry);
			if (e == null)
				continue;
			attachNodePath(nameParts, off, e.parent);
			return e;
		}
		return null;
	}

	/**
	 * Merges the nodes for building the path specifided by <code>nameParts</code> with the currently existing nodes.
	 * 
	 * @param nameParts The file name splitted up into directories
	 * @param off The offset where to start in the <code>nameParts</code> array
	 * @param jar The jar to register on the new nodes
	 */
	private void mergeNodePath(final String[] nameParts, final int off, final Jar jar) {
		Node parent = this;
		// create the sub node entries for the retrived entry
		for (int i = off; i < nameParts.length - 1; i++) {
			final Node child = parent.childs.get(nameParts[i]);
			// if no such subnode exists then we can savely call attach
			if (child == null) {
				attachNodePath(nameParts, i, jar);
				return;
			}
			child.addJar(jar);
			parent = child;
		}
	}

	/**
	 * Attaches the nodes for building the path specifided by <code>nameParts</code> at this node.<br>
	 * Use {@link #mergeNodePath(String[], int, Jar)} if some nodes may allready exist.
	 * 
	 * @param nameParts The file name splitted up into directories
	 * @param off The offset where to start in the <code>nameParts</code> array
	 * @param jar The jar to register on the new nodes
	 */
	private void attachNodePath(final String[] nameParts, final int off, final Jar jar) {
		Node parent = this;
		// create the sub node entries for the retrived entry
		for (int i = off; i < nameParts.length - 1; i++)
			parent = parent.addNode(nameParts[i], new Node(jar));
	}

	/**
	 * Searches all jars in {@link #jars} for the given file and returns the first matching entry.
	 * 
	 * @param name A file name (with / as package seperator)
	 * @param entry An {@link Entry} object where to put the result
	 * @return The passed entry object containing the resolved entry or null if nothing was found
	 */
	private Entry searchEntry(String name, Entry entry) {
		for (Jar f : jars) {
			final Entry e = f.getEntry(name, entry);
			if (e != null)
				return e;
		}
		return null;
	}
}